package fgin

import (
	"context"
	"fmt"
	"strings"
	"time"

	"gitee.com/wu-jin-feng/fgin/fgin_plugs/fgorm/flogger"
	"gitee.com/wu-jin-feng/fgin/fgin_plugs/fotel/ftracer"
	"github.com/gin-gonic/gin"
	"github.com/opentracing/opentracing-go"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/trace"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	glogger "gorm.io/gorm/logger"
)

var fdbMap = make(map[string]*gorm.DB)

func initGorm() {
	if len(fginConfig.Mysql) == 0 {
		// 没有填写mysql配置则不进行初始化
		return
	}
	var newGormLog glogger.Interface
	if fginConfig.GormLog != (GormLog{}) {
		// 配置文件如果没有gorm log的配置，则配置为空
		newGormLog = initGormLog()
	}

	for _, v := range fginConfig.Mysql {
		dsn := v.Target
		db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
			Logger:                 newGormLog,
			SkipDefaultTransaction: v.SkipDefaultTran,
			PrepareStmt:            v.PrepareStmt,
		})
		if err != nil {
			panic("mysql初始化失败: " + err.Error())
		}
		// 是否开启otel mysql的链路追踪
		if fginConfig.Otel.EnableTracerDB {
			if err := db.Use(&gormOtelTracingPlugin{}); err != nil {
				panic("otel mysql链路追踪开启失败: " + err.Error())
			}
		}
		// 是否开启mysql的链路追踪
		if fginConfig.Jaeger.EnableDB {
			if err := db.Use(&gormTracingPlugin{}); err != nil {
				panic("mysql链路追踪开启失败: " + err.Error())
			}
		}
		if v.MaxIdle > 0 && v.MaxOpen > 0 && v.MaxLift > 0 {
			// 构建连接池
			// 获取通用数据库对象 sql.DB ，然后使用其提供的功能
			sqlDB, err := db.DB()
			if err != nil {
				panic("mysql连接池失败: " + err.Error())
			}
			// SetMaxIdleConns 用于设置连接池中空闲连接的最大数量。
			sqlDB.SetMaxIdleConns(v.MaxIdle)
			// SetMaxOpenConns 设置打开数据库连接的最大数量。
			sqlDB.SetMaxOpenConns(v.MaxOpen)
			// SetConnMaxLifetime 设置了连接可复用的最大时间。
			sqlDB.SetConnMaxLifetime(time.Millisecond * time.Duration(v.MaxLift))
		}
		fdbMap[v.Name] = db
	}
}

func initGormLog() glogger.Interface {
	return flogger.New(
		&fginGormLog{},
		// log.New(os.Stdout, "\r\n", log.LstdFlags),
		glogger.Config{
			SlowThreshold:             time.Duration(fginConfig.GormLog.SlowThreshold) * time.Millisecond,
			LogLevel:                  glogger.LogLevel(fginConfig.GormLog.LogLevel),
			IgnoreRecordNotFoundError: fginConfig.GormLog.IgnoreRecordNotFoundError,
			ParameterizedQueries:      fginConfig.GormLog.ParameterizedQueries,
			Colorful:                  fginConfig.GormLog.Colorful,
		},
	)
	// 初始化gorm的日志输出
	// return glogger.New(
	// 	&fginGormLog{},
	// 	// log.New(os.Stdout, "\r\n", log.LstdFlags),
	// 	glogger.Config{
	// 		SlowThreshold:             time.Duration(fginConfig.GormLog.SlowThreshold) * time.Millisecond,
	// 		LogLevel:                  glogger.LogLevel(fginConfig.GormLog.LogLevel),
	// 		IgnoreRecordNotFoundError: fginConfig.GormLog.IgnoreRecordNotFoundError,
	// 		ParameterizedQueries:      fginConfig.GormLog.ParameterizedQueries,
	// 		Colorful:                  fginConfig.GormLog.Colorful,
	// 	},
	// )
}

type fginGormLog struct{}

// data中存在Context剔除出来
func (fgLog *fginGormLog) getDataNoHaveContext(data ...any) ([]any, context.Context) {
	var (
		ctx   context.Context
		isOk  bool
		index int
	)
	// 遍历获取ctx
	for i, arg := range data {
		v, ok := arg.(*gin.Context)
		if ok {
			ctx = v
			index = i
			isOk = true
		}
		v1, ok1 := arg.(context.Context)
		if ok1 {
			ctx = v1
			index = i
			isOk = true
		}
		if isOk {
			break
		}
	}
	if !isOk {
		return data, ctx
	}
	// 有获取到ctx
	newArgs := []any{}
	newArgs = append(newArgs, data[0:index]...)
	newArgs = append(newArgs, data[index+1:]...)
	return newArgs, ctx
}

func (fgLog *fginGormLog) Printf(msg string, data ...any) {
	newData, ctx := fgLog.getDataNoHaveContext(data...)
	sqlStr := fmt.Sprintf(msg, newData...)
	if fginConfig.GormLog.LogLevel <= 2 {
		logSuger.Error(sqlStr, ctx)
	} else if fginConfig.GormLog.LogLevel == 3 {
		logSuger.Warn(sqlStr, ctx)
	} else if fginConfig.GormLog.LogLevel == 4 {
		logSuger.Info(sqlStr, ctx)
	}
}

// gorm 链路追踪的插件
const (
	gormSpanKey       = "opentracing:span" // 纯jaeger客户端
	gormOtelTracerKey = "otel:tracer"      // opentelemetry sdk 链路追踪 tracer
	gormOtelSpanKey   = "otel:span"        // opentelemetry sdk 链路追踪 span
)

// 用于otel的链路追踪
type gormOtelTracingPlugin struct{}

func (tp *gormOtelTracingPlugin) Name() string {
	return "gormOtelTracingPlugin"
}

func (tp *gormOtelTracingPlugin) Initialize(db *gorm.DB) error {
	// 注册回调
	db.Callback().Create().Before("*").Register("fgin_tracing:before_create", tp.beforeCreate)
	db.Callback().Query().Before("*").Register("fgin_tracing:before_query", tp.beforeQuery)
	db.Callback().Update().Before("*").Register("fgin_tracing:before_update", tp.beforeUpdate)
	db.Callback().Delete().Before("*").Register("fgin_tracing:before_delete", tp.beforeDelete)
	db.Callback().Row().Before("*").Register("fgin_tracing:before_row", tp.beforeRow)
	db.Callback().Raw().Before("*").Register("fgin_tracing:before_raw", tp.beforeRaw)

	db.Callback().Create().After("*").Register("fgin_tracing:after_create", tp.after)
	db.Callback().Query().After("*").Register("fgin_tracing:after_query", tp.after)
	db.Callback().Update().After("*").Register("fgin_tracing:after_update", tp.after)
	db.Callback().Delete().After("*").Register("fgin_tracing:after_delete", tp.after)
	db.Callback().Row().After("*").Register("fgin_tracing:after_row", tp.after)
	db.Callback().Raw().After("*").Register("fgin_tracing:after_raw", tp.after)

	return nil
}

// trace 初始化
func (tp *gormOtelTracingPlugin) traceInit(db *gorm.DB, name string) {
	ctx := db.Statement.Context
	if ctx == nil {
		return
	}
	// 存jaeger 配置
	tracer := ftracer.NewTracer(trace.SpanKindClient, "DB")
	_, span := tracer.Start(ctx, "gorm."+name)
	// 设置属性
	span.SetAttributes(
		attribute.String("method", "DB"),
	)
	// 存储tracer到DB实例
	db.InstanceSet(gormOtelTracerKey, tracer)
	db.InstanceSet(gormOtelSpanKey, span)
}

// Create
func (tp *gormOtelTracingPlugin) beforeCreate(db *gorm.DB) {
	tp.traceInit(db, "Create")
}

// Query
func (tp *gormOtelTracingPlugin) beforeQuery(db *gorm.DB) {
	tp.traceInit(db, "Query")
}

// Update
func (tp *gormOtelTracingPlugin) beforeUpdate(db *gorm.DB) {
	tp.traceInit(db, "Update")
}

// Delete
func (tp *gormOtelTracingPlugin) beforeDelete(db *gorm.DB) {
	tp.traceInit(db, "Delete")
}

// Row
func (tp *gormOtelTracingPlugin) beforeRow(db *gorm.DB) {
	tp.traceInit(db, "Row")
}

// Raw
func (tp *gormOtelTracingPlugin) beforeRaw(db *gorm.DB) {
	tp.traceInit(db, "Raw")
}

// 钩子结束
func (tp *gormOtelTracingPlugin) after(db *gorm.DB) {
	// 获取 tracer span
	_tracer, ok := db.InstanceGet(gormOtelTracerKey)
	if !ok {
		return
	}
	tracer, ok := _tracer.(*ftracer.Tracer)
	if !ok || tracer == nil {
		return
	}
	_span, ok := db.InstanceGet(gormOtelSpanKey)
	if !ok {
		return
	}
	span, ok := _span.(trace.Span)
	if !ok || span == nil {
		return
	}
	// 提交日志
	span.AddEvent("db_handle", trace.WithAttributes(
		attribute.String("sql", db.Statement.SQL.String()),
		attribute.String("table", db.Statement.Table),
	))
	defer tracer.End(span, db.Error)
}

// 纯jaeger的链路追踪
type gormTracingPlugin struct{}

func (tp *gormTracingPlugin) Name() string {
	return "gormTracingPlugin"
}

func (tp *gormTracingPlugin) Initialize(db *gorm.DB) error {
	// 注册回调
	db.Callback().Create().Before("gorm:before_create").Register("fgin_tracing:before_create", tp.before)
	db.Callback().Query().Before("gorm:before_query").Register("fgin_tracing:before_query", tp.before)
	db.Callback().Update().Before("gorm:before_update").Register("fgin_tracing:before_update", tp.before)
	db.Callback().Delete().Before("gorm:before_delete").Register("fgin_tracing:before_delete", tp.before)
	db.Callback().Row().Before("gorm:before_row").Register("fgin_tracing:before_row", tp.before)
	db.Callback().Row().Before("gorm:before_raw").Register("fgin_tracing:before_raw", tp.before)

	db.Callback().Create().After("gorm:after_create").Register("fgin_tracing:after_create", tp.after)
	db.Callback().Query().After("gorm:after_query").Register("fgin_tracing:after_query", tp.after)
	db.Callback().Update().After("gorm:after_update").Register("fgin_tracing:after_update", tp.after)
	db.Callback().Delete().After("gorm:after_delete").Register("fgin_tracing:after_delete", tp.after)
	db.Callback().Row().After("gorm:after_row").Register("fgin_tracing:after_row", tp.after)
	db.Callback().Row().After("gorm:after_raw").Register("fgin_tracing:after_raw", tp.after)

	return nil
}

func (tp *gormTracingPlugin) before(db *gorm.DB) {
	ctx := db.Statement.Context
	if ctx == nil {
		return
	}
	sqlOperation := strings.Split(db.Statement.SQL.String(), " ")[0]
	// 存jaeger 配置
	span := JaegerSpan("gorm."+sqlOperation, ctx)
	if span == nil {
		return
	}
	span.SetTag("method", "DB")
	span.LogKV("sql", db.Statement.SQL.String(), "table", db.Statement.Table)
	// 存储Span到DB实例
	db.InstanceSet(gormSpanKey, span)
}

func (tp *gormTracingPlugin) after(db *gorm.DB) {
	// 获取Span
	_span, ok := db.InstanceGet(gormSpanKey)
	if !ok {
		return
	}

	span, ok := _span.(opentracing.Span)
	if !ok || span == nil {
		return
	}
	defer span.Finish()
	if db.Error != nil {
		span.SetTag("error", true)
		span.LogKV("error", db.Error.Error())
	}
}
